package top.lidee.common.service.twice.third.wechat.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import top.lidee.common.bean.WxPayV3Bean;
import top.lidee.common.dto.AccountInfo;
import top.lidee.common.service.StoreService;
import top.lidee.common.service.twice.management.*;
import top.lidee.common.service.twice.third.wechat.WechatPayJJCommonService;
import top.lidee.common.service.twice.third.wechat.WechatPaySftService;
import top.lidee.common.to.wx.*;
import top.lidee.framework.enums.CommonExceptionEnum;
import top.lidee.framework.result.GlobalCheck;
import top.lidee.framework.result.ResponseResult;
import top.lidee.repository.model.MtStore;
import top.lidee.repository.model.twice.pojo.applet.MtStoreCateEntity;
import top.lidee.repository.model.twice.pojo.management.*;
import top.lidee.utils.MyCollUtil;


import java.net.URLDecoder;
import java.security.cert.*;
import java.util.*;
import java.util.stream.Collectors;

import static top.lidee.framework.constant.RedisConstant.PRIVATE_BANK;
import static top.lidee.framework.constant.RedisConstant.PUBLIC_BANK;
import static top.lidee.framework.constant.WechatConstant.*;


/**
 * @author 张一恒
 * @version 1.0
 * @description: 微信收款 平台收付通（二级商户进件）
 * @date 2024/9/30 16:26
 */
@Service("wechatPaySftService")
@Slf4j
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class WechatPaySftServiceImpl implements WechatPaySftService {

    //微信V3支付Bean
    private final WxPayV3Bean wxPayV3Bean;

    //微信 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
    private final CloseableHttpClient wxPayNoSignClient;

    //店铺业务接口
    private final StoreService storeService;

    //微信省市区编号对照表
    private final MtProvincesNumTableService mtProvincesNumTableService;

    private final WechatPayJJCommonService wechatPayJJCommonService;

    private final StringRedisTemplate stringRedisTemplate;


    /**
     * @param storeId
     * @param accountInfo
     * @return top.lidee.framework.result.ResponseResult
     * @description 电商二级商户进件（提交申请单）
     * accountBank 开户银行
     * * bankName 开户银行全称 （含支行）
     * @author zhangyiheng
     * @date 2024/9/30 16:29
     */
    @Override
    public ResponseResult wxIncomingParts(Integer storeId, AccountInfo accountInfo) {
        GlobalCheck.checkManagerAdmin(accountInfo.getManagerId());

        //获取店铺证照信息
        MtStore store = storeService.getById(storeId);

        TpbWechatBankAccountEntity tpbWechatBankAccount = null;
        MtStoreCateEntity cate = null;
        List<MtUploadCateCertificationLogEntity> mtUploadCateCertificationLogs = null;
        MtStoreWechatInputPieceLogEntity pieceLog = null;
        Map<String, Object> map = wechatPayJJCommonService.checkInfo(accountInfo.getManagerId(), store);
        if (CollUtil.isNotEmpty(map)) {
            if (Objects.nonNull(map.get("tpbWechatBankAccount"))) {
                tpbWechatBankAccount = (TpbWechatBankAccountEntity) map.get("tpbWechatBankAccount");
            }

            if (Objects.nonNull(map.get("cate"))) {
                cate = (MtStoreCateEntity) map.get("cate");
            }

            if (Objects.nonNull(map.get("mtUploadCateCertificationLogs"))) {
                mtUploadCateCertificationLogs = (List<MtUploadCateCertificationLogEntity>) map.get("mtUploadCateCertificationLogs");
            }

            if (Objects.nonNull(map.get("pieceLog"))) {
                pieceLog = (MtStoreWechatInputPieceLogEntity) map.get("pieceLog");
            }
        }

        //请求URL
        HttpPost httpPost = new HttpPost(wxPayV3Bean.getWxIncomingPartsUrl());

        // 请求body参数
        WxIncomingPartsTo to = new WxIncomingPartsTo();

        String start = DateUtil.format(store.getLicenseStartTime(), "yyyy-MM-dd");
        String end = DateUtil.format(store.getLicenseEndTime(), "yyyy-MM-dd");
        //获取证书
        X509Certificate certificate = wechatPayJJCommonService.getCertificate(wxPayV3Bean.getCertificatePath());

        //region Description  业务申请编号 	string[1,124] 必填
        /*
        1、服务商自定义的商户唯一编号。
        2、每个编号对应一个申请单，每个申请单审核通过后会生成一个微信支付商户号。
        3、若申请单被驳回，可填写相同的“业务申请编号”，即可覆盖修改原申请单信息 。
         */
        theBusinessRequisitionNumber(to, store);
        //endregion


        //region Description 主体类型 string[1,4]  必填
        /*
        非小微的主体类型需与营业执照/登记证书上一致，可参考选择主体指引，枚举值如下。
        2401：小微商户，指无营业执照的个人商家。
        4：个体工商户，营业执照上的主体类型一般为个体户、个体工商户、个体经营。
        2：企业，营业执照上的主体类型一般为有限公司、有限责任公司。
         */
        bodyType(store, to);
        //endregion


        //region Description 是否金融机构  boolean	条件选填
        /*
        选填，请根据申请主体的实际情况填写，可参考选择金融机构指引：
        1、若商户主体是金融机构，则填写：true。
        2、若商户主体不是金融机构，则填写：false。
        若未传入将默认填写：false。
        示例值：true
         */
        whetherItIsAFinancialInstitution(to);
        //endregion

        //region Description 营业执照/登记证书信息 object  条件选填
        /*
        1、主体为“小微/个人卖家”时，不填。
        2、主体为“个体工商户/企业”时，请上传营业执照。
         */
        registrationCertificateInformation(store, start, end, to);
        //endregion


        //region 金融机构许可证信息 object 条件选填 当主体是金融机构时，必填
//        to.setFinance_institution_info();
        //endregion


        //region  证件持有人类型 string 条件选填
        // 2. 主体类型为企业/个体户/社会组织时，默认为经营者/法人，不需要上传该字段。
//        to.setId_holder_type();
        //endregion


        //region Description 经营者/法人证件类型 string 	条件选填
        /*
        1、当证件持有人类型为经营者/法人时，需要填写。其他情况，无需上传。
        2、主体为“小微/个人卖家”，可选择：身份证。
        3、主体为“个体户/企业/事业单位/社会组织”：可选择任一证件类型，主体为“政府机关”仅支持中国大陆居民-身份证类型。
        4、若没有填写，系统默认选择：身份证。
         */
        //目前默认都是身份证
        theTypeOfLegalEntityCertificate(store, to);
        //endregion


        //region Description 法定代表人说明函  string[1,256] 	条件选填
        //1、当证件持有人类型为经办人时，必须上传。其他情况，无需上传。
//        to.setAuthorize_letter_copy();
        //endregion


        //region Description 经营者/法人身份证信息 object 	条件选填
        //当证件持有人类型为经营者/法人且证件类型为“身份证”时填写。
        legalPersonIDCardInformation(store, start, end, to);
        //endregion


        //region - 经营者/法人其他类型证件信息 object 	条件选填
        // 当证件持有人类型为经营者/法人且证件类型不为“身份证”时填写。
//        to.setId_doc_info();
        //endregion


        //region Description 经营者/法人是否为受益人 	owner 	boolean 	条件选填
        //主体类型为企业时，需要填写：
        //1、若经营者/法人是最终受益人，则填写：true。
        //2、若经营者/法人不是最终受益人，则填写：false。
        whetherTheLegalPersonIsABeneficiary(store, to);
        //endregion

        //region  最终受益人信息列表 array 	条件选填 仅企业需要填写。
        //若经营者/法人不是最终受益所有人，则需提填写受益所有人信息，最多上传4个。
        //若经营者/法人是最终受益所有人之一，可在此填写其他受益所有人信息，最多上传3个。
//        if (Objects.equals(store.getPrincipalType(), ENTERPRISE)) {
//            to.setUbo_info_list();
//        }
        //endregion


        //region Description 结算账户信息 object 必填
        billingAccountInformation(store, certificate, to, tpbWechatBankAccount);
        //endregion


        //region Description 超级管理员信息 	contact_info 	object 	必选
        //超级管理员需在开户后进行签约，并可接收日常重要管理信息和进行资金操作，请确定其为商户法定代表人或负责人。
        superAdminInformation(store, certificate, to, tpbWechatBankAccount);
        //endregion


        //region Description 店铺信息 sales_scene_info 	object 	必选
        storeInformation(to);
        //endregion


        //region Description 结算规则 	settlement_info 	object 	非必填
        billingRules(to, store, cate);
        //endregion


        //region Description 商户简称 必选
        //将在支付完成页向买家展示，需与商家的实际售卖商品相符 。 示例值：腾讯
        merchantAbbreviation(to, store);
        //endregion


        //region Description 特殊资质 非必须
        //1、仅当商户选择了必需提交特殊资质的行业时，需要提供该项资料；若商户选择了无需特殊资质的行业，或未选择行业时，无需提交该项资料，详情查看《行业对应特殊资质》
        //3、最多可上传5张照片， 示例值：[\"jTpGmxUX3FBWVQ5NJInE4d2I6_H7I4\"]
        specialQualifications(mtUploadCateCertificationLogs, to);
        //endregion


        //region Description 补充材料  预付宝业务必传 根据实际审核情况，额外要求提供。最多可上传15张照片，
        //TODO 在店铺信息字段中需要进件店铺二维码，二级商户进件api没有审核通过的话，商家的店铺不会在小程序展示。需要有商家信息或平台介绍凭证等以图片的方式上传
        supplementaryMaterials(to);
        //endregion


        //region  1、若主体为“个人卖家”，该字段必传，则需填写描述“ 该商户已持续从事电子商务经营活动满6个月，且期间经营收入累计超过20万元。”
//        to.setBusiness_addition_desc("");
        //endregion

        //构建body
        return wechatPayJJCommonService.doWXRES(storeId, to, httpPost, store, "收付通", pieceLog);
    }


    /**
     * @param to
     * @return void
     * @description 补充材料    business_addition_pics 	string[1,1024] 	非必须
     * 根据实际审核情况，额外要求提供。最多可上传15张照片，请填写通过图片上传接口预先上传图片生成好的MediaID 。
     * 示例值：[\"jTpGmg05InE4d2I6_H7I4\"]
     * @author zhangyiheng
     * @date 2024/10/7 11:41
     */
    private void supplementaryMaterials(WxIncomingPartsTo to) {
        to.setBusiness_addition_pics("");
    }

    /**
     * @param mtUploadCateCertificationLogs
     * @param to
     * @return void
     * @description 特殊资质    qualifications 	string[1,1024] 	非必须
     * 1、仅当商户选择了必需提交特殊资质的行业时，需要提供该项资料；若商户选择了无需特殊资质的行业，或未选择行业时，无需提交该项资料，详情查看《行业对应特殊资质》
     * 2、请提供为“申请商家主体”所属的特殊资质，可授权使用总公司/分公司的特殊资质；
     * 3、最多可上传5张照片，请填写通过图片上传接口预先上传图片生成好的MediaID 。
     * 示例值：[\"jTpGmxUX3FBWVQ5NJInE4d2I6_H7I4\"]
     * 在店铺信息字段中需要进件店铺二维码，二级商户进件api没有审核通过的话，商家的店铺不会在小程序展示。需要有商家信息或平台介绍凭证等以图片的方式上传
     * @author zhangyiheng
     * @date 2024/10/7 11:39
     */
    private void specialQualifications(List<MtUploadCateCertificationLogEntity> mtUploadCateCertificationLogs, WxIncomingPartsTo to) {
        int count;
        if (MyCollUtil.isNotEmpty(mtUploadCateCertificationLogs)) {
            StringBuilder sb = new StringBuilder();
            sb.append("[");
            count = 0;
            for (MtUploadCateCertificationLogEntity mtUploadCateCertificationLog : mtUploadCateCertificationLogs) {
                String imageMediaId = wechatPayJJCommonService.wxGetImageMediaId(mtUploadCateCertificationLog.getImageUrl());
                sb.append("\"").append(imageMediaId).append("\",");
                count++;
                if (Objects.equals(count, 5)) {
                    break;
                }
            }
            sb.deleteCharAt(sb.length() - 1);
            sb.append("]");
            to.setQualifications(sb.toString());
        }
    }

    /**
     * @param to
     * @param store
     * @return void
     * @description 商户简称    merchant_shortname 	string[1,64] 	必选
     * UTF-8格式，中文占3个字节，即最多21个汉字长度。将在支付完成页向买家展示，需与商家的实际售卖商品相符 。
     * 示例值：腾讯
     * @author zhangyiheng
     * @date 2024/10/7 11:39
     */
    private void merchantAbbreviation(WxIncomingPartsTo to, MtStore store) {
        to.setMerchant_shortname(store.getName());
    }

    /**
     * @param to
     * @return void
     * @description 结算规则    settlement_info 	object 	非必填
     * 请填写商家的结算费率规则、所属行业等信息。若电商平台未传入，系统将填写默认值
     * @author zhangyiheng
     * @date 2024/10/7 11:38
     */
    private void billingRules(WxIncomingPartsTo to, MtStore store, MtStoreCateEntity cate) {
        //填写商家的结算费率规则、所属行业等信息。若电商平台未传入，系统将填写默认值
        settlement_info settlement_info = new settlement_info();

        Integer settlementId = null;
        switch (store.getPrincipalType()) {
            case SOLE_PROPRIETORSHIP:
                //个体
                settlementId = cate.getRulesIndividual();
                break;
            case ENTERPRISE:
                //企业
                settlementId = cate.getRulesEnterprise();
                break;
            case SOHO:
                //小微/个人
                settlementId = cate.getRulesTiny();
                break;
            default:
                break;
        }
        //结算规则ID
        /*
        1、选填，请选择二级商户的结算规则ID，需匹配电商平台开通工具箱选择的费率档位，详细参见电商二级商户结算规则对照表；
        2、若电商平台未传入，将默认选择0.6%费率对应的结算规则id；
         */
        if (!Objects.isNull(settlementId)) {
            settlement_info.setSettlement_id(settlementId);
        }

        //所属行业
        /*
        1、选填，请填写二级商户所属的行业名称，映射特殊资质要求，详细参见电商二级商户结算规则对照表；
        2、若电商平台未传入，将默认填写无需特殊资质的行业名称；
         */
        settlement_info.setQualification_type(cate.getWechatCateName());
        to.setSettlement_info(settlement_info);
    }

    /**
     * @param to
     * @return void
     * @description - 店铺信息 	sales_scene_info 	object 	必选
     * 请填写店铺信息
     * @author zhangyiheng
     * @date 2024/10/7 11:36
     */
    private void storeInformation(WxIncomingPartsTo to) {
        sales_scene_info sales_scene_info = new sales_scene_info();

        //店铺名称
        sales_scene_info.setStore_name("预付宝");

        //TODO 店铺二维码 （备注：需要预付宝小程序二维码）
        /*
        1、店铺二维码 or 店铺链接二选一必填。
        2、若为电商小程序，可上传店铺页面的小程序二维码。
         */
        //预付宝为微信小程序，直接选择小程序二维码 后续补充说明
        //没有进件通过不存在店铺二维码，传入平台小程序二维码 所有生成的小程序码永久有效 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/qr-code.html
        //参考：https://developers.weixin.qq.com/community/pay/doc/000e66586a4d6081abeac70b05a400
        sales_scene_info.setStore_qr_code(wechatPayJJCommonService.wxGetImageMediaId(""));

        //TODO 小程序AppID （备注：需要预付宝小程序AppID）
        /*
        1、商户自定义字段，可填写已认证的小程序AppID，认证主体需与二级商户主体一致；
        2、完成入驻后， 系统发起二级商户号与该AppID的绑定（即配置为sub_appid，可在发起支付时传入）
         */
        sales_scene_info.setMini_program_sub_appid("");

        to.setSales_scene_info(sales_scene_info);
    }

    /**
     * @param store
     * @param certificate
     * @param to
     * @return void
     * @description - 超级管理员信息 	contact_info 	object 	必选
     * 请填写店铺的超级管理员信息。
     * 超级管理员需在开户后进行签约，并可接收日常重要管理信息和进行资金操作，请确定其为商户法定代表人或负责人
     * @author zhangyiheng
     * @date 2024/10/7 11:34
     */
    private void superAdminInformation(MtStore store, X509Certificate certificate, WxIncomingPartsTo to, TpbWechatBankAccountEntity tpbWechatBankAccount) {
        contact_info contact_info = new contact_info();

        //超级管理员类型 必选
        /*
        1、主体为“小微/个人卖家 ”，可选择：65-经营者/法人。
        2、主体为“个体工商户/企业/政府机关/事业单位/社会组织”，可选择：65-经营者/法人、66- 经办人。 （经办人：经商户授权办理微信支付业务的人员）。
        示例值：65
         */
        contact_info.setContact_type("65");


        //超级管理员姓名 必选
        /*
        该字段需进行加密处理，
        1、若管理员类型为“法人”，则该姓名需与法人身份证姓名一致。
        2、若管理员类型为“经办人”，则可填写实际负责人的姓名。
         */
        contact_info.setContact_name(wechatPayJJCommonService.rsaEncryptOAEP(store.getContact(), certificate));


        //超级管理员证件类型  当超级管理员类型是经办人时，请上传超级管理员证件类型。
//        contact_info.setContact_id_doc_type();


        //超级管理员证件号码 条件选填
        /*
        1、若超级管理员类型为法人，则该身份证号码需与法人身份证号码一致。若超级管理员类型为经办人，则可填写实际经办人的身份证号码。
        3、超级管理员签约时，校验微信号绑定的银行卡实名信息，是否与该证件号码一致。
        4、该字段需进行加密处理，
         */
        //注意：超级管理员签约时，校验微信号绑定的银行卡实名信息，是否与该证件号码一致。
        contact_info.setContact_id_card_number(wechatPayJJCommonService.rsaEncryptOAEP(store.getIdCard(), certificate));


        //超级管理员证件正面照片 超级管理员类型是经办人时上传
        /*
        1、当超级管理员类型是经办人时，请上传超级管理员证件的正面照片。
        2、若证件类型为身份证，请上传人像面照片。
         */
//        contact_info.setContact_id_doc_copy();

        //超级管理员证件反面照片 超级管理员类型是经办人时上传
        //1、当超级管理员类型是经办人时，请上传超级管理员证件的反面照片。
//        contact_info.setContact_id_doc_copy_back();

        //超级管理员证件有效期开始时间 超级管理员类型是经办人时上传
//        contact_info.setContact_id_doc_period_begin();
        //超级管理员证件有效期结束时间 超级管理员类型是经办人时上传
//        contact_info.setContact_id_doc_period_end();
        //业务办理授权函 超级管理员类型是经办人时上传
//        contact_info.setBusiness_authorization_letter();

        //超级管理员手机 必选
        //该字段需进行加密处理
        contact_info.setMobile_phone(wechatPayJJCommonService.rsaEncryptOAEP(store.getPhone(), certificate));

        //超级管理员邮箱 条件选填
        /*
        1、主体类型为“小微商户/个人卖家”可选填，其他主体需必填。
        2、用于接收微信支付的开户邮件及日常业务通知。
        3、需要带@，遵循邮箱格式校验 。
        4、该字段需进行加密处理，
         */
        contact_info.setContact_email(wechatPayJJCommonService.rsaEncryptOAEP(tpbWechatBankAccount.getEmail(), certificate));

        to.setContact_info(contact_info);
    }

    /**
     * @param store
     * @param certificate
     * @param to
     * @return void
     * @description 结算账户信息    account_info 	object 	必填
     * 请填写商家提现收款的银行账户信息
     * @author zhangyiheng
     * @date 2024/10/7 11:31
     */
    private void billingAccountInformation(MtStore store, X509Certificate certificate, WxIncomingPartsTo to, TpbWechatBankAccountEntity tpbWechatBankAccount) {
        //请填写商家提现收款的银行账户信息
        account_info account_info = new account_info();

        //账户类型 必填
        //1、若主体为企业/政府机关/事业单位/社会组织，可填写：74-对公账户。
        //2、主体为小微/个人卖家，可选择：75-对私账户。
        //3、若主体为个体工商户，可填写：74-对公账户、75-对私账户。
        switch (store.getPrincipalType()) {
            case SOHO:
                // 小微/个人卖家
                account_info.setBank_account_type("75");
                break;
            case SOLE_PROPRIETORSHIP:
                // 个体工商户
                if (Objects.equals(tpbWechatBankAccount.getUsageType(), TO_THE_PUBLIC)) {
                    account_info.setBank_account_type("74");
                } else {
                    account_info.setBank_account_type("75");
                }
                break;
            case ENTERPRISE:
                // 企业
                account_info.setBank_account_type("74");
                break;
            default:
        }

        // 开户银行 必填
        //对私银行调用：查询支持个人业务的银行列表API
        //对公银行调用：查询支持对公业务的银行列表API。
        //示例值：工商银行
        account_info.setAccount_bank(tpbWechatBankAccount.getAccountBank());


        //开户名称 必填
        //1、选择经营者个人银行卡时，开户名称必须与身份证姓名一致。
        //2、选择对公账户时，开户名称必须与营业执照上的“商户名称”一致。
        //3、该字段需进行加密处理，
        switch (store.getPrincipalType()) {
            case SOHO:
                // 小微/个人卖家 75-对私账户。
                account_info.setAccount_name(wechatPayJJCommonService.rsaEncryptOAEP(store.getContact(), certificate));
                break;
            case SOLE_PROPRIETORSHIP:
                // 个体工商户
                if (Objects.equals(tpbWechatBankAccount.getUsageType(), TO_THE_PUBLIC)) {
                    //74-对公账户
                    account_info.setAccount_name(wechatPayJJCommonService.rsaEncryptOAEP(store.getLicenseName(), certificate));
                } else {
                    //75-对私账户。
                    account_info.setAccount_name(wechatPayJJCommonService.rsaEncryptOAEP(store.getContact(), certificate));
                }
                break;
            case ENTERPRISE:
                // 企业 74-对公账户
                account_info.setAccount_name(wechatPayJJCommonService.rsaEncryptOAEP(store.getLicenseName(), certificate));
                break;
            default:
        }

        //开户银行省市编码 必填 至少精确到市
        /*
        注：
            仅当省市区编号对照表中无对应的省市区编号时，可向上取该银行对应市级编号或省级编号。
        示例值：110000
         */
        MtProvincesNumTableEntity bean = mtProvincesNumTableService.getOne(Wrappers.<MtProvincesNumTableEntity>lambdaQuery().eq(MtProvincesNumTableEntity::getAreaName, tpbWechatBankAccount.getAccountInstProvince() + "," + tpbWechatBankAccount.getAccountInstCity()));
        if (Objects.isNull(bean)) {
            bean = mtProvincesNumTableService.getOne(Wrappers.<MtProvincesNumTableEntity>lambdaQuery().eq(MtProvincesNumTableEntity::getAreaName, tpbWechatBankAccount.getAccountInstProvince()));
        }
        account_info.setBank_address_code(bean.getProvincialCode());

        // 开户银行联行号 特殊可选，根据是否需要填写支行条件
        /*
        1、根据开户银行查询接口中的“是否需要填写支行”判断是否需要填写。如为其他银行，开户银行全称（含支行）和开户银行联行号二选一。
        2、详细需调用查询支行列表API查看查询结果。
        示例值：402713354941
         */
//        account_info.setBank_branch_id();


        //开户银行全称 （含支行）开户银行全称 （含支行） 	bank_name 	string[1,128] 	条件选填 和上面（开户银行联行号）二选一
        account_info.setBank_name(tpbWechatBankAccount.getAccountBranchName());

        //银行账号 2、该字段需进行加密处理， 必选
        //对公对私卡号长度对照表 https://pay.weixin.qq.com/docs/partner/development/chart/bank-of-deposit.html
        account_info.setAccount_number(wechatPayJJCommonService.rsaEncryptOAEP(tpbWechatBankAccount.getBankCardNumber(), certificate));

        to.setAccount_info(account_info);

    }

    /**
     * @param store
     * @param to
     * @return void
     * @description 经营者/法人是否为受益人 	owner 	boolean 	条件选填
     * 主体类型为企业时，需要填写：
     * 1、若经营者/法人是最终受益人，则填写：true。
     * 2、若经营者/法人不是最终受益人，则填写：false。
     * 示例值：true
     * @author zhangyiheng
     * @date 2024/10/7 11:27
     */
    private void whetherTheLegalPersonIsABeneficiary(MtStore store, WxIncomingPartsTo to) {
        if (Objects.equals(store.getPrincipalType(), ENTERPRISE)) {
            to.setOwner(true);
        }
    }

    /**
     * @param store
     * @param start
     * @param end
     * @param to
     * @return void
     * @description 经营者/法人身份证信息 	id_card_info 	object 	条件选填
     * 当证件持有人类型为经营者/法人且证件类型为“身份证”时填写。
     * @author zhangyiheng
     * @date 2024/10/7 11:26
     */
    private void legalPersonIDCardInformation(MtStore store, String start, String end, WxIncomingPartsTo to) {
        id_card_info id_card_info = new id_card_info();
        //身份证人像面照片 	id_card_copy 	string[1,256] 	必填
        id_card_info.setId_card_copy(wechatPayJJCommonService.wxGetImageMediaId(store.getCardVersaUrl()));

        //身份证国徽面照片
        id_card_info.setId_card_national(wechatPayJJCommonService.wxGetImageMediaId(store.getCardUpUrl()));

        //身份证姓名
        id_card_info.setId_card_name(store.getContact());

        //身份证号码
        id_card_info.setId_card_number(store.getIdCard());

        //身份证居住地址 1、主体类型为企业时，需要填写。其他主体类型，无需上传。
        id_card_info.setId_card_address(store.getIdCardAddress());

        //身份证开始时间 1、日期格式应满足合法的YYYY-MM-DD格式
        id_card_info.setId_card_valid_time_begin(start);

        //身份证结束时间 1、日期格式应满足合法的YYYY-MM-DD格式
        id_card_info.setId_card_valid_time(end);
        to.setId_card_info(id_card_info);
    }

    /**
     * @param store
     * @param to
     * @return void
     * @description 经营者/法人证件类型 	id_doc_type 	string 	条件选填
     * 1、当证件持有人类型为经营者/法人时，需要填写。其他情况，无需上传。
     * 2、主体为“小微/个人卖家”，可选择：身份证。
     * 3、主体为“个体户/企业/事业单位/社会组织”：可选择任一证件类型，主体为“政府机关”仅支持中国大陆居民-身份证类型。
     * 4、若没有填写，系统默认选择：身份证。
     * 枚举值：
     * IDENTIFICATION_TYPE_MAINLAND_IDCARD：中国大陆居民-身份证
     * IDENTIFICATION_TYPE_OVERSEA_PASSPORT：其他国家或地区居民-护照
     * IDENTIFICATION_TYPE_HONGKONG：中国香港居民--来往内地通行证
     * IDENTIFICATION_TYPE_MACAO：中国澳门居民--来往内地通行证
     * IDENTIFICATION_TYPE_TAIWAN：中国台湾居民--来往大陆通行证
     * IDENTIFICATION_TYPE_FOREIGN_RESIDENT：外国人居留证
     * IDENTIFICATION_TYPE_HONGKONG_MACAO_RESIDENT：港澳居民证
     * IDENTIFICATION_TYPE_TAIWAN_RESIDENT：台湾居民证
     * 示例值：IDENTIFICATION_TYPE_MAINLAND_IDCARD
     * @author zhangyiheng
     * @date 2024/10/7 11:24
     */
    private void theTypeOfLegalEntityCertificate(MtStore store, WxIncomingPartsTo to) {
        switch (store.getPrincipalType()) {
            case SOHO: //小微/个人卖家
                to.setId_doc_type("IDENTIFICATION_TYPE_MAINLAND_IDCARD");
                break;
            case SOLE_PROPRIETORSHIP:
            case ENTERPRISE:
                //个体户/企业
                to.setId_doc_type("IDENTIFICATION_TYPE_MAINLAND_IDCARD");
                break;
            default:
                break;
        }
    }


    /**
     * @param store
     * @param start
     * @param end
     * @param to
     * @return void
     * @description 营业执照/登记证书信息 	business_license_info 	object 	条件选填
     * 1、主体为“小微/个人卖家”时，不填。
     * 2、主体为“个体工商户/企业”时，请上传营业执照。
     * 3、主体为“政府机关/事业单位/社会组织”时，请上传登记证书。
     * @author zhangyiheng
     * @date 2024/10/7 11:23
     */
    private void registrationCertificateInformation(MtStore store, String start, String end, WxIncomingPartsTo to) {
        if (!Objects.equals(store.getPrincipalType(), SOHO)) {
            business_license_info business_license_info = new business_license_info();
            //证书类型1、主体为“政府机关/事业单位/社会组织”时，请上传登记证书类型。 2、主体为“个体工商户/企业”时，不填。
//            business_license_info.setCert_type();

            //营业执照扫描件  必填 主体为“个体工商户/企业”时，请上传营业执照的证件图片;
            //可上传1张图片，请填写通过图片上传API预先上传图片生成好的MediaID 。
            business_license_info.setBusiness_license_copy(wechatPayJJCommonService.wxGetImageMediaId(store.getLicense()));

            //营业执照注册号 营业执照上的注册号/统一社会信用代码
            business_license_info.setBusiness_license_number(store.getCreditCode());

            //商户名称
            business_license_info.setMerchant_name(store.getLicenseName());

            //经营者/法定代表人姓名
            business_license_info.setLegal_person(store.getContact());

            //注册地址 建议填写营业执照的注册地址，若该字段未填写，系统将会查询国家工商信息填入。需注意若工商信息查询不到，则会被审核驳回。
            business_license_info.setCompany_address(store.getAddress());

            //建议填写，若该字段未填写，系统将会查询国家工商信息填入。需注意若工商信息查询不到，则会被审核驳回。
            //日期格式应满足合法的YYYY-MM-DD格式。
            //示例值：["2014-01-01","长期"]
            String time = "[\"" + start + "\"" + "," + "\"" + end + "\"]";
            business_license_info.setBusiness_time(time);
            to.setBusiness_license_info(business_license_info);
        }
    }

    /**
     * @param to
     * @return void
     * @description 是否金融机构    finance_institution 	boolean 	条件选填
     * 选填，请根据申请主体的实际情况填写，可参考选择金融机构指引：
     * 1、若商户主体是金融机构，则填写：true。
     * 2、若商户主体不是金融机构，则填写：false。
     * 若未传入将默认填写：false。
     * 示例值：true
     * @author zhangyiheng
     * @date 2024/10/7 11:23
     */
    private void whetherItIsAFinancialInstitution(WxIncomingPartsTo to) {
        to.setFinance_institution(false);
    }


    /**
     * @param store
     * @param to
     * @return void
     * @description 主体类型    organization_type 	string[1,4] 	必填
     * 非小微的主体类型需与营业执照/登记证书上一致，可参考选择主体指引，枚举值如下。
     * 2401：小微商户，指无营业执照的个人商家。
     * 2500：个人卖家，指无营业执照，已持续从事电子商务经营活动满6个月，且期间经营收入累计超过20万元的个人商家。（若选择该主体，请在“补充说明”填写相关描述）。
     * 4：个体工商户，营业执照上的主体类型一般为个体户、个体工商户、个体经营。
     * 2：企业，营业执照上的主体类型一般为有限公司、有限责任公司。
     * 3：事业单位，包括国内各类事业单位，如：医疗、教育、学校等单位。
     * 2502：政府机关，包括各级、各类政府机关，如机关党委、税务、民政、人社、工商、商务、市监等。
     * 1708：社会组织，包括社会团体、民办非企业、基金会、基层群众性自治组织、农村集体经济组织等组织。
     * 示例值：2
     * @author zhangyiheng
     * @date 2024/10/7 11:22
     */
    private void bodyType(MtStore store, WxIncomingPartsTo to) {
        switch (store.getPrincipalType()) {
            case SOHO://个人
                to.setOrganization_type("2401");
                break;
            case SOLE_PROPRIETORSHIP:
                to.setOrganization_type("4");
                break;
            case ENTERPRISE:
                to.setOrganization_type("2");
                break;
            default:
                break;
        }
    }


    /**
     * @param to
     * @param store
     * @return void
     * @description 业务申请编号    out_request_no 	string[1,124] 	必填
     * 1、服务商自定义的商户唯一编号。
     * 2、每个编号对应一个申请单，每个申请单审核通过后会生成一个微信支付商户号。
     * 3、若申请单被驳回，可填写相同的“业务申请编号”，即可覆盖修改原申请单信息 。
     * 示例值：APPLYMENT_00000000001
     * @author zhangyiheng
     * @date 2024/10/7 11:21
     */
    private void theBusinessRequisitionNumber(WxIncomingPartsTo to, MtStore store) {
        to.setOut_request_no(store.getExternalId());
    }


    /**
     * @param next
     * @param accountInfo
     * @return top.lidee.framework.result.ResponseResult
     * @description 微信对私银行调用：查询支持个人业务的银行列表API
     * 注意：
     * • 如需检索，请在前端缓存所有银行数据并自行完成检索功能；
     * • 业务查询到的数据为后端缓存快照，业务方需要定期淘汰并更新数据；
     * • 单商户单接口最大请求频率不超过300TPS；
     * • 【银行别名编码】和【银行别名】字段仅用于前端展示、检索支行；
     * • 微信支付入驻、修改结算银行卡、付款到银行卡等接口中需要使用【开户银行编码】和【开户银行】字段；
     * @author zhangyiheng
     * @date 2024/10/15 09:09
     */
    @Override
    public ResponseResult callToPrivateBank(String next, AccountInfo accountInfo) {

        Map<String, String> map = getOffsetAndLimit(next);
        String offset = map.get("offset");
        String limit = map.get("limit");
        String url = wxPayV3Bean.getCallToPrivateBankUrl();
        //查询redis
        String privateBank = stringRedisTemplate.opsForValue().get(PRIVATE_BANK + offset + "_" + limit);
        PersonalBanking personalBanking = null;
        if (StrUtil.isBlank(privateBank)) {
            url = url + "?offset=" + offset + "&limit=" + limit;
            privateBank = commonGetPush(url);
            //存入redis
            stringRedisTemplate.opsForValue().set(PRIVATE_BANK + offset + "_" + limit, JSON.toJSONString(personalBanking));
        }

        personalBanking = JSON.parseObject(privateBank, PersonalBanking.class);

        return ResponseResult.okResult(personalBanking);
    }


    /**
     * @param next
     * @param accountInfo
     * @return top.lidee.framework.result.ResponseResult
     * @description 对公银行调用：查询支持对公业务的银行列表API
     * @author zhangyiheng
     * @date 2024/10/15 10:07
     */
    @Override
    public ResponseResult callToPublicBank(String next, AccountInfo accountInfo) {

        Map<String, String> map = getOffsetAndLimit(next);
        String offset = map.get("offset");
        String limit = map.get("limit");
        String url = wxPayV3Bean.getCallToPublicBankUrl();
        //查询redis
        String publiceBank = stringRedisTemplate.opsForValue().get(PUBLIC_BANK + offset + "_" + limit);
        PersonalBanking personalBanking = null;
        if (StrUtil.isBlank(publiceBank)) {
            url = url + "?offset=" + offset + "&limit=" + limit;
            publiceBank = commonGetPush(url);
            //存入redis
            stringRedisTemplate.opsForValue().set(PUBLIC_BANK + offset + "_" + limit, JSON.toJSONString(personalBanking));
        }

        personalBanking = JSON.parseObject(publiceBank, PersonalBanking.class);

        return ResponseResult.okResult(personalBanking);
    }


    /**
     * @param next
     * @return java.util.Map<java.lang.String, java.lang.String>
     * @description getOffsetAndLimit
     * @author zhangyiheng
     * @date 2024/10/15 10:16
     */
    private Map<String, String> getOffsetAndLimit(String next) {
        String offset = "0";
        String limit = "200";
        if (StrUtil.isNotBlank(next)) {
            Map<String, Object> map = urlPTurnMap(next);
            offset = (String) map.get("offset");
            limit = (String) map.get("limit");
        }
        Map<String, String> map = new HashMap<>();
        map.put("offset", offset);
        map.put("limit", limit);
        return map;
    }


    /**
     * @param next
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @description 解析url参数 转map
     * @author zhangyiheng
     * @date 2024/10/15 11:49
     */
    private Map<String, Object> urlPTurnMap(String next) {
        if (!next.contains("?")) {
            throw new BeanCreationException(CommonExceptionEnum.PARAM_ERROR.getMsg());
        }
        //解析url参数 转map
        String[] split = next.split("\\?");
        String urlData = split[1];
        String data = null;
        try {
            data = URLDecoder.decode(urlData, "UTF-8");
        } catch (Exception e) {
            log.error("参数解码失败:{}", e.getMessage(), e);
        }
        Map<String, Object> map = Arrays.stream(data.split("&"))
                .map(str -> str.split("="))
                .collect(Collectors.toMap(x -> x[0], x -> x.length == 2 ? x[1] : "",
                        (u, v) -> {
                            throw new IllegalStateException(String.format("Duplicate key %s", u));
                        }, LinkedHashMap::new));
        return map;
    }

    /**
     * @param url
     * @return top.lidee.common.to.wx.PersonalBanking
     * @description 通用get请求方法
     * @author zhangyiheng
     * @date 2024/10/15 09:12
     */
    private String commonGetPush(String url) {
        //请求URL
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

        //完成签名并执行请求
        try (CloseableHttpResponse response = wxPayNoSignClient.execute(httpGet)) {
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功
                String body = EntityUtils.toString(response.getEntity());
//                log.info("personal-banking:{}", personalBanking);
                if (StrUtil.isBlank(body)) {
                    log.error("请求查询支持个人业务的银行列表处理成功，无返回Body");
                    throw new BeanCreationException("第三方服务异常，请刷新后重试！");
                }
                return body;
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.error("请求查询支持个人业务的银行列表处理成功，无返回Body");
                throw new BeanCreationException("第三方服务异常，请刷新后重试！");
            } else {
                log.error("请求查询支持个人业务的银行列表处理失败 failed,resp code = {},return body = {}", statusCode, EntityUtils.toString(response.getEntity()));
                throw new BeanCreationException("第三方服务异常，请刷新后重试！");
            }
        } catch (Exception e) {
            log.error("请求查询支持个人业务的银行列表API错误：{}", e.getMessage(), e);
            throw new BeanCreationException("第三方服务异常，请刷新后重试！");
        }
    }

    /**
     * @param provinceCode
     * @param accountInfo
     * @return top.lidee.framework.result.ResponseResult
     * @description 查询城市列表API
     * @author zhangyiheng
     * @date 2024/10/15 11:12
     */
    @Override
    public ResponseResult queryTheListOfCities(String provinceCode, AccountInfo accountInfo) {

        String url = StrUtil.format(wxPayV3Bean.getQueryTheListOfCitiesUrl(), provinceCode);

        String body = commonGetPush(url);
        Map map = JSON.parseObject(body, Map.class);
        List<Map<String, String>> data = (List<Map<String, String>>) map.get("data");

        if (!MyCollUtil.isNotEmpty(data)) {
            return ResponseResult.errorResult(CommonExceptionEnum.SYS_BUSY);
        }

        return ResponseResult.okResult(data);
    }

    /**
     * @param bankAliasCode
     * @param cityCode
     * @param accountInfo
     * @return top.lidee.framework.result.ResponseResult
     * @description * 查询支行列表API
     * * bankAliasCode:银行别名编码
     * * cityCode:城市编码
     * @author zhangyiheng
     * @date 2024/10/15 11:32
     */
    @Override
    public ResponseResult queryBranchList(String bankAliasCode, String cityCode, String next, AccountInfo accountInfo) {

        Map<String, String> map = getOffsetAndLimit(next);
        String offset = map.get("offset");
        String limit = map.get("limit");
        String url = StrUtil.format(wxPayV3Bean.getQueryBranchListUrl(), bankAliasCode) + "?city_code=" + cityCode + "&offset=" + offset + "&limit=" + limit;

        String body = commonGetPush(url);
        QueryBranchListTo queryBranchListTo = JSON.parseObject(body, QueryBranchListTo.class);

        Optional.ofNullable(queryBranchListTo).orElseThrow(() -> new BeanCreationException(CommonExceptionEnum.SYS_BUSY.getMsg()));

        return ResponseResult.okResult(queryBranchListTo);
    }

    public static void main(String[] args) {
        String time = "/v3/capital/capitallhh/banks/personal-banking?offset=10&limit=5";
//        X509Certificate certificate = new LazyX509Certificate()
//        System.out.println(rsaEncryptOAEP(time,));
    }
}
